/* STRING data type */

/* Depends on xmalloc & xrealloc to be provided by the caller
   (Note: xrealloc must call xmalloc if handed a NULL pointer) */

/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org

    Copyright (C) 1999-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/* Compile test program with:
   cc -g -o string string.c -DTEST
*/

#include <string.h>
#include <ctype.h>
#include "mylib.h"

STRING newString ARG1(int, maxlen)
{
    STRING	retval = NULL;
    
    if (maxlen >= 0) {
	retval = (STRING) xmalloc((INDEX) sizeof(struct STRING_S));
	retval->str = xmalloc((INDEX) (maxlen + 1));
	retval->str[0] = 0;
	retval->maxlen = maxlen;
	retval->len = 0;
    }
    return(retval);
}

int StringLength ARG1(STRING, s)
{
    if (s)
	return(s->len);
    else
	return(0);
}

int StringMaxLength ARG1(STRING, s)
{
    if (s)
	return(s->maxlen);
    else
	return(0);
}

char *StringToStr ARG1(STRING, s)
{
    assert(s);
    
    return(s->str);
}

static void resizeString ARG2(STRING, dest, int, newlen)
{
    assert(dest);

    if ((newlen != dest->maxlen) && (newlen >= dest->len)) {
	dest->str = xrealloc(dest->str, newlen + 1);
	dest->maxlen =  newlen;
    }
}

void delCharInString ARG2(STRING, dest, int, pos)
{
    char	*d, *s;
    
    assert(dest);
    assert(dest->str);

    if (pos == -1)
	pos = dest->len - 1;

    if (dest->len && (pos >= 0) && (pos < dest->len)) {
	
	d = dest->str + pos;
	s = d + 1;
	while ((*d++ = *s++))
	    ;
	(dest->len)--;
	resizeString(dest, dest->len);
    }
}

/* Yes, you can add a char to the end of the string if pos >= dest->len.
   setCharInString(dest, -1, p) sets the last character */
void setCharInString ARG3(STRING, dest, int, pos, char, p)
{
    assert(dest);
    assert(dest->str);
    assert(p);

    if (pos == -1) {
        if (dest->len)
            pos = dest->len - 1;
        else
            pos = 0;
    }
    
    assert(pos >= 0);
	
    if (pos >= dest->len) {
        int		oldlen = dest->len, i;

        resizeString(dest, pos + 1);
        for (i = oldlen; i < pos; i++, (dest->len)++)
            dest->str[i] = ' ';
        (dest->len)++;
        dest->str[pos + 1] = 0;
    }
    dest->str[pos] = p;
}

/* Inserts 'p' into the string at position pos. Extends string with
   spaces if not long enough.  -1 inserts at the end. */
void insCharInString ARG3(STRING, dest, int, pos, char, p)
{
    int		i, oldlen, newMaxLen;
    
    assert(dest);
    assert(dest->str);
    assert(p);

    if (pos == -1)
	pos = dest->len;

    assert(pos >= 0);

    oldlen = dest->len;

    if (pos > dest->maxlen)
        newMaxLen = pos + 1;
    else
        newMaxLen = dest->maxlen + 1;

    resizeString(dest, newMaxLen);

    if (pos >= oldlen) {
	for (i = oldlen; i < pos; i++, (dest->len)++)
	    dest->str[i] = ' ';
	dest->str[pos + 1] = 0;
    } else
	for (i = oldlen + 1; i > pos; i--)
	    dest->str[i] = dest->str[i - 1];
	
    (dest->len)++;
    dest->str[pos] = p;
}
    
void StringCpy ARG2(STRING, dest, STRING, source)
{
    assert(source);
    assert(source->str);
    assert(dest);

    resizeString(dest, source->len);
	
    strcpy(dest->str, source->str);
    dest->len = source->len;
}

void StringCat ARG2(STRING, dest, STRING, source)
{
    assert(dest);
    assert(source);
    assert(source->str);

    if (*(source->str)) {
	int	newlen = dest->len + source->len;

	resizeString(dest, newlen);

	strcat(dest->str, source->str);
	dest->len = newlen;
    }
}

void truncString ARG2(STRING, dest, int, pos)
{
    assert(dest);
    assert(dest->str);
    assert(pos >= 0);

    if (pos < dest->len) {
	dest->str[pos] = 0;
	dest->len = pos;
	resizeString(dest, pos);
    }
}

STRING subString ARG3(STRING, source, int, start, int, end)
{
    STRING	retval = NULL;

    assert(source);
    assert(source->str);
    assert(start <= source->len);

    if ((end == -1) || (end > source->len))
	end = source->len;

    assert(start >= 0);
    assert(start <= source->len);
    assert(end >= 0);
    assert(end >= start);

    retval = newString(end - start);
    strncpy(retval->str, source->str + start, end - start);
    retval->len = end - start;
    retval->str[retval->len] = 0;
    return(retval);
}

STRING StrToString ARG2(STRING, dest, char *, s)
{
    int		len;
    STRING	retval = dest;

    assert(s);

    len = strlen(s);
    if (dest) {
	if (dest->maxlen < len)
	    resizeString(dest, len);

	retval = dest;
    } else
	retval = newString(len);
    retval->len = len;
    strcpy(retval->str, s);
    return(retval);
}

int rTrimString ARG1(STRING, dest)
{
    int		retval = 0;

    assert(dest);

    if (dest->str) {
        while (dest->len && isspace(dest->str[dest->len - 1]))
            dest->str[--(dest->len)] = 0;
        resizeString(dest, dest->len);
        retval = dest->len;
    }
    return(retval);
}

int lTrimString ARG1(STRING, dest)
{
    int		retval = 0;
    char	*d, *s;

    assert(dest);

    if (dest->str) {
        while (dest->len && isspace(dest->str[0])) {
            for (d = dest->str, s = d + 1; (*d++ = *s++);)
                ;
            (dest->len)--;
        }
        resizeString(dest, dest->len);
        retval = dest->len;
    }
    return(retval);
}

int trimString ARG1(STRING, dest)
{
    int		retval = 0;

    assert(dest);

    if ((retval = rTrimString(dest)))
	retval = lTrimString(dest);
    return(retval);
}

/* Mainly for use in removeAllItemsFromArray ... */
void freeStringF ARG1(STRING, s)
{
    assert(s);

    freeString(s);
}

#ifdef TEST
#include <stdio.h>

/* extern jmp_buf		NoMoreHeap; 
  - don't bother with exceptons for the test program */

char *xmalloc ARG1(INDEX, size)
{
    register char 	*retval;

    if ((retval = malloc(size)))
	return(retval);
    /* Problem - no more heap */
    /* longjmp(NoMoreHeap, 1); */
    return(NULL);
}

char *xrealloc ARG2(char *, oldbuf, unsigned int, size)
{
    register char 	*retval;

    if (oldbuf) {
	if ((retval = realloc(oldbuf, size)))
	    return(retval);
    } else
	return(xmalloc((INDEX) size));

    /* Problem - no more heap */
    /* longjmp(NoMoreHeap, 1); */
    return(NULL);
}

printfStr ARG1(STRING, s)
{
    if (s)
	printf("\"%s\" length = %d, maxlen = %d\n", 
	       StringToStr(s), StringLength(s), StringMaxLength(s));
}

main()
{
    STRING 	a,b,c;

    b = StrToString(NULL, "Hello world");
    printfStr(b);

    a = newString(10);
    StringCpy(a, b);
    printfStr(a);

    StringCat(a, b);
    printfStr(a);

/*
    StringCat(a, a);
    printfStr(a);
*/

    StrToString(a, "New");
    printfStr(a);

    printf("Insert char\n");
    StrToString(a, "Bottom");
    printfStr(a);
    insCharInString(a, 0, '1');
    printfStr(a);
    insCharInString(a, 2, '2');
    printfStr(a);
    insCharInString(a, -1, '3');
    printfStr(a);
    insCharInString(a, 12, '4');
    printfStr(a);
    insCharInString(a, -1, '4');
    printfStr(a);

    printf("Delete char\n");
    delCharInString(a, 0);
    printfStr(a);
    delCharInString(a, 2);
    printfStr(a);
    delCharInString(a, -1);
    printfStr(a);
    delCharInString(a, 500);
    printfStr(a);
    delCharInString(a, -2);
    printfStr(a);

    printf("Set char\n");
    StrToString(a, "Bottom");
    setCharInString(a, 0, 'b');
    printfStr(a);
    setCharInString(a, 2, 'b');
    printfStr(a);
    setCharInString(a, -1, 'b');
    printfStr(a);
    setCharInString(a, 12, 'b');
    printfStr(a);

    printf("subString\n");
    StrToString(a, "Bottom");
    c = subString(a, 0, 3);
    printfStr(c);
    freeString(c);
    c = subString(a, 1, 3);
    printfStr(c);
    freeString(c);
    c = subString(a, 1, -1);
    printfStr(c);
    freeString(c);
    c = subString(a, 0, 300);
    printfStr(c);
    freeString(c);
    freeString(a);
    freeString(b);
}
#endif

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
